#include "Ex-2-Func.h"

#define CURRENTDIR "C:/Users/YU_IT/Desktop/vscode/CompilerPrinciple/"

typedef enum {
  Begine = 1,
  If,
  Then,
  While,
  Do,
  End,
  /* key words */

  Words = 10,
  /* Letters */
  Digits,
  /* Digits */

  Plus = 13,
  Minus,
  Multi,
  Divide,
  Colon,
  ColonEq,
  /* operator */

  Less = 20,
  AngleBrackets,
  LessEq,
  Great,
  GreatEq,
  Equal,
  Semicolon,
  LeftBracket,
  RightBracket,
  /* operator */

  WellNumber = 0
  /* # */
} Code;

/* KeyWord */
const char *keyword[] = {"begin\0", "if\0", "then\0", "while\0", "do\0", "end\0"};

/* 二元组 */
typedef struct {
    /* 种别码 */
    Code code;
    /* 占位符 */
    char token[LINE_NUMBERS];
} WordGroup;

/* 当前指示器 */
size_t _next = 0;
/* 当前字符串 */
char tokens[LINE_NUMBERS] = {'\0'};
/* 当前字符 */
char current_char;
/* 当前行 */
size_t current_line;

/* 获取当前字符 */
#define GetChar() (*(source->src[current_line]+_next))

/* 从读入的缓冲区的当前行中读入一个字符 */
void GetNextChar() {
    assert(current_line < source->line_nums); 
    current_char = *(source->src[current_line] + (_next++));
}

/* 判断分隔符，如果为分隔符则继续知道不是分隔符 */
void GetNextDel() {
    assert(current_line < source->line_nums); 
    while (current_char == '\n') {
        current_line++, _next = 0;
        if (current_line == source->line_nums) {
            return;
        }
        GetNextChar();
    }
    while(current_char == ' ' || current_char == '\t') {
        GetNextChar();
    } 
}

/* 连接构造字符 */
void ConcatTokens() {
    assert(current_line < source->line_nums);
    tokens[strlen(tokens)] = current_char;
}

/* 查找关键字表 */
int SearchReserve() {
    for (int i = 0; i < 6; i++) {
        if (!strcmp(keyword[i], tokens)) 
            return i + 1;
    }
    return Words;
}

/* 回退 */
void Retract() {
    _next--;
    current_char = '\0';
}

/* 错误处理 */
void ErrorHandle(const char * fileName) {
    char info[255];
    assert(current_line < source->line_nums);
    sprintf_s(info, 255, "[%s%s]: Err(%zd): %s", CURRENTDIR,  fileName, current_line+1, source->src[current_line]);
    printf(info);
    _flushall();
}

/* 忽略注释 */
void IgnoreComment() {
    current_line++;
    _next = 0;
    current_char = '\0';
}

/* 词组定义 */
WordGroup word[WORD_NUMS];
size_t word_index = 0;

/* 输出处理完成的字符 */
char output[WORD_NUMS] = {'\0'};
void Convert() {
    memset(output, 0, sizeof(char) * WORD_NUMS);
    size_t len = 0;
    for (size_t i = 0; i < word_index; i++) {
        size_t bufLen;
        char buffer[20];
        sprintf_s(buffer, 20, "(%d, %s)\0", word[i].code, word[i].token);
        bufLen = strlen(buffer);
        /*
         ! 当i=18，执行到这个位置的时候，执行下列拷贝语句，全局变量source的内存会突然消失
         ! source因此变成野指针，造成内存泄漏，这是为什么？
         ! memmove(output + len, buffer, strlen(buffer));
         ! memcpy(output + len, buffer, strlen(buffer));
         ! sprintf_s(output + len, WORD_NUMS, "%s\n", buffer);

         * 解决方法，把问题消灭掉：在执行这个函数之前调用Release函数先把source内存释放，
         * 并将source置空，然后在Init中判断source是否不是NULL以及INITIALIZATION是否为0或者1，
         * 以此来确定这两个全局变量是否被意外改写了。
        */ 
        sprintf_s(output + len, WORD_NUMS, "%s\n", buffer);
        len++;
        len += bufLen;
    }
}


/* 初始化词组 */
void InitWordGroup() {
    word_index = 0;
    for (size_t i = 0; i < WORD_NUMS; i++) {
        word[i].code = WellNumber;
        memset(word[i].token, 0, sizeof(char) * LINE_NUMBERS);
    }
}

/* Assign Word group */
void AddWordGroup(Code code) {
    size_t len = strlen(tokens);
    word[word_index].code = code;
    memcpy(word[word_index].token, tokens, len);
    memset(tokens, 0, sizeof(char) * len);
    word_index++;
}


void Reset() {
    current_char = '\0';
    current_line = 0;
    _next = 0;
    InitWordGroup();
}

#define ADDTOKENWORD(code) ConcatTokens();AddWordGroup(code);
#define ADDTOKEN ConcatTokens();GetNextChar();
#define RETRACTTOKEN(code) Retract();AddWordGroup(code);

/* 扫描主逻辑 */
char * Scanner(const char * fileName) {
    Reset();
    while (GetChar() != '\n' || current_line < source->line_nums) {
        Code code;
        memset(tokens, 0, sizeof(char) * LINE_NUMBERS);
        GetNextChar();
        GetNextDel();
        if (isalpha(current_char)) {
            while (isalnum(current_char)) {
                ADDTOKEN
            }
            code = SearchReserve();
            Code tmp;
            /* 当前token为关键字 */
            if (code != Words) {
                tmp = code;
            } else { /* 当前token为标识符 */
                tmp = Words;
            }
            RETRACTTOKEN(tmp)
        } else if(isdigit(current_char)) {
            char tmp = 0;
            do {
                if (isdigit(current_char)) {
                    ADDTOKEN
                }
                else if (isalpha(current_char)) {
                    ErrorHandle(fileName);
                    tmp = 1;
                    break;
                }
            } while (isalnum(current_char));
            if (tmp == 0) {
                RETRACTTOKEN(Digits)
            }
        } 
        else if(current_char == '+') {
            ADDTOKENWORD(Plus)
        } 
        else if(current_char == '-') {
            ADDTOKENWORD(Minus);
        } 
        else if(current_char == '*') {
            ADDTOKENWORD(Multi);
        } 
        else if(current_char == '/') {
            ADDTOKENWORD(Divide);
        } 
        else if(current_char == ':') {
            ADDTOKEN
            if (current_char == '=') {
                ADDTOKENWORD(ColonEq);
            } else {
                RETRACTTOKEN(Colon)
            }
        } 
        else if(current_char == '<') {
            ADDTOKEN
            if (current_char == '>') {
                ADDTOKENWORD(AngleBrackets)
            } else if(current_char == '=') {
                ADDTOKENWORD(LessEq)
            } else {
                RETRACTTOKEN(Less)
            }
        } else if(current_char == '>') {
            ADDTOKEN
            if (current_char == '=') {
                ADDTOKENWORD(GreatEq)
            } else {
                RETRACTTOKEN(Great)
            }
        } 
        else if(current_char == '=') {
            ADDTOKENWORD(Equal)
        }
        else if(current_char == ';') {
            ADDTOKENWORD(Semicolon)
        }
        else if(current_char == '(') {
            ADDTOKENWORD(LeftBracket)
        }
        else if(current_char == ')') {
            ADDTOKENWORD(RightBracket)
        }
        else if(current_char == '#') {
            ADDTOKENWORD(WellNumber)
            IgnoreComment();
        } else if (current_line == source->line_nums || GetChar() == '\0') {
            // eof
            break;
        }
        else ErrorHandle(fileName);
    }
    Release();
    Convert();
    return output;
}